#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xos.h>
#include "asusosd_image.h"
#include "readpng.h"   /* typedefs, common macros, readpng prototypes */

#define alpha_composite(composite, fg, alpha, bg) {               \
    unsigned short temp = ((unsigned short)(fg)*(unsigned short)(alpha) +                          \
                (unsigned short)(bg)*(unsigned short)(255 - (unsigned short)(alpha)) + (unsigned short)128);  \
    (composite) = (unsigned char)((temp + (temp >> 8)) >> 8);               \
}

/* local prototypes */
static int  xwindow_create(void);
static int  ximage_create(void);
static int  ximage_display(void);
void destroy_osd(void);
//static void destroy_image(void);
static int  x_msb(unsigned long u32val);

static unsigned char	bg_red=0, bg_green=0, bg_blue=0;
static unsigned char	*image_data;
static unsigned long	image_width, image_height, image_rowbytes;
static int		image_channels;
static double		display_exponent;

/* X-specific variables */
static XImage		*ximage;
static Display		*display;
static int		depth;
static Visual		*visual;
static int		RShift, GShift, BShift;
static unsigned long	RMask, GMask, BMask;
static Window		window;
static GC		gc;
static Colormap		colormap;

static int have_colormap = FALSE;
static int have_window = FALSE;
static int have_gc = FALSE;

int create_image(char* filename)
{
	FILE *pngfile;

	if (!filename)
		return -1;
	else if (!(pngfile = fopen(filename, "rb"))) 
	{
		printf(":  can't open PNG file [%s]\n", filename);
		return -1;
	} 
	else {
		if ((readpng_init(pngfile, &image_width, &image_height)) != 0)
		{
			fclose(pngfile);
			return -1;
		}
	}


	if (readpng_get_bgcolor(&bg_red, &bg_green, &bg_blue) > 1) {
		readpng_cleanup(TRUE);
		printf(":  libpng error while checking for background color\n");
		return -2;
	}

	if (xwindow_create())
		return -2;

	if(ximage_create())
	{
		destroy_osd();
		return -2;
	}
	/* decode the image, all at once */

	image_data = readpng_get_image(display_exponent, &image_channels, &image_rowbytes);

	readpng_cleanup(FALSE);

	fclose(pngfile);

	if (!image_data) {
		printf(":  unable to decode PNG image\n");
		destroy_osd();
		return -3;
	}


	/* display image (composite with background if requested) */
	if (ximage_display()) {
		//free(image_data);
		destroy_osd();
		return -4;
	}

	return 0;
}

int close_image(void)
{
	if(have_window)
	{
		XUnmapWindow(display, window);
		XFlush(display);
		if (ximage) {
			if (ximage->data) {
				free(ximage->data);           // we allocated it, so we free it 
				ximage->data = (char *)NULL;  //  instead of XDestroyImage() 
			}
			XDestroyImage(ximage);
			ximage = NULL;
		}

	}
	else	return -1;
	return 0;
}

void destroy_osd(void)
{
	if (image_data) {
		free(image_data);
		image_data = NULL;
	}

	if (ximage) {
		if (ximage->data) {
			free(ximage->data);           // we allocated it, so we free it 
			ximage->data = (char *)NULL;  //  instead of XDestroyImage() 
		}
		XDestroyImage(ximage);
		ximage = NULL;
	}

	if (have_gc)
	{
		XFreeGC(display, gc);
		have_gc = FALSE;
	}

	if (have_window)
	{
		XDestroyWindow(display, window);
		have_window = FALSE;
	}

	if (have_colormap)
	{
		XFreeColormap(display, colormap);
		have_colormap = FALSE;
	}
}

int screen, pad;


static int xwindow_create(void)
{
	int need_colormap = FALSE;
	unsigned long bg_pixel = 0L;
	unsigned long attrmask;
	Window root;
//	XEvent e;
	XGCValues gcvalues;
	XSetWindowAttributes attr;
	XSizeHints *size_hints;
	XTextProperty windowName, *pWindowName = &windowName;
	XTextProperty iconName, *pIconName = &iconName;
	XVisualInfo visual_info;
	XWMHints *wm_hints;
	extern int xpos, ypos;
	double LUT_exponent;               /* just the lookup table */
	double CRT_exponent = 2.2;         /* just the monitor */

	if(have_window ==FALSE)
	{
		display_exponent = LUT_exponent * CRT_exponent;
		display = XOpenDisplay(NULL);
		if (!display) {
			readpng_cleanup(TRUE);return 2;
		}

		screen = DefaultScreen(display);
		depth = DisplayPlanes(display, screen);
		root = RootWindow(display, screen);
		XMatchVisualInfo(display, screen, depth, TrueColor, &visual_info);
		visual = DefaultVisual(display, screen);//visual_info.visual;

		RMask = visual->red_mask;
		GMask = visual->green_mask;
		BMask = visual->blue_mask;

		/* GRR:  add/check 8-bit support */
		if (depth == 8 || need_colormap) {
			colormap = XCreateColormap(display, root, visual, AllocNone);
			if (!colormap) {
				printf("XCreateColormap() failed\n");
				return 2;
			}
			have_colormap = TRUE;
		}
		if (depth == 15 || depth == 16) {
			RShift = 15 - x_msb(RMask);    /* these are right-shifts */
			GShift = 15 - x_msb(GMask);
			BShift = 15 - x_msb(BMask);
		} else if (depth > 16) {
#define NO_24BIT_MASKS
#ifdef NO_24BIT_MASKS
			RShift = x_msb(RMask) - 7;     /* these are left-shifts */
			GShift = x_msb(GMask) - 7;
			BShift = x_msb(BMask) - 7;
#else
			RShift = 7 - x_msb(RMask);     /* these are right-shifts, too */
			GShift = 7 - x_msb(GMask);
			BShift = 7 - x_msb(BMask);
#endif
		}
		if (depth >= 15 && (RShift < 0 || GShift < 0 || BShift < 0)) {
			printf("rpng internal logic error:  negative X shift(s)!\n");
			return 2;
		}

		/*---------------------------------------------------------------------------
		  Finally, create the window.
		  ---------------------------------------------------------------------------*/

		attrmask = CWEventMask | CWBackPixel | CWBorderPixel |  CWOverrideRedirect;//CWBackingStore | CWEventMask;
		attr.override_redirect = 1;
		attr.background_pixel  = 0xffff;
		attr.event_mask = ExposureMask | StructureNotifyMask | ButtonPressMask | KeyPressMask;
		attr.border_pixel = 0;
		//xpos = (XDefaultScreenOfDisplay(display)->width - image_width) -2;
		//ypos = (XDefaultScreenOfDisplay(display)->height - image_height) -2;
		window = XCreateWindow(display, root, xpos, ypos, image_width, image_height, 0,
				depth, InputOutput, visual, attrmask, &attr);

		if (window == None) {
			printf("XCreateWindow() failed\n");
			return 2;
		} else
			have_window = TRUE;

		if (depth == 8)
			XSetWindowColormap(display, window, colormap);

		pWindowName = NULL;
		pIconName = NULL;

		/* OK if either hints allocation fails; XSetWMProperties() allows NULLs */

		if ((size_hints = XAllocSizeHints()) != NULL) {
			/* window will not be resizable */
			size_hints->flags = PMinSize | PMaxSize;
			size_hints->min_width = size_hints->max_width = (int)image_width;
			size_hints->min_height = size_hints->max_height = (int)image_height;
		}

		if ((wm_hints = XAllocWMHints()) != NULL) {
			wm_hints->initial_state = NormalState;
			wm_hints->input = True;
			/* wm_hints->icon_pixmap = icon_pixmap; */
			wm_hints->flags = StateHint | InputHint  /* | IconPixmapHint */ ;
		}

		XSetWMProperties(display, window, pWindowName, pIconName, NULL, 0,
				size_hints, wm_hints, NULL);

		/* various properties and hints no longer needed; free memory */
		if (pWindowName)
			XFree(pWindowName->value);
		if (pIconName)
			XFree(pIconName->value);
		if (size_hints)
			XFree(size_hints);
		if (wm_hints)
			XFree(wm_hints);

		XMapWindow(display, window);

		gc = XCreateGC(display, window, 0, &gcvalues);
		have_gc = TRUE;

		/*---------------------------------------------------------------------------
		  Fill window with the specified background color.
		  ---------------------------------------------------------------------------*/

		if (depth == 24 || depth == 32) {

			bg_pixel = ((unsigned long)bg_red   << RShift) |
				((unsigned long)bg_green << GShift) |
				((unsigned long)bg_blue  << BShift);
		} else if (depth == 16) {
			bg_pixel = ((((unsigned long)bg_red   << 8) >> RShift) & RMask) |
				((((unsigned long)bg_green << 8) >> GShift) & GMask) |
				((((unsigned long)bg_blue  << 8) >> BShift) & BMask);
		} else /* depth == 8 */ {

			/* GRR:  add 8-bit support */

		}

		XSetForeground(display, gc, 0);
		XFillRectangle(display, window, gc, 0, 0, image_width, image_height);

		/*---------------------------------------------------------------------------
		  Wait for first Expose event to do any drawing, then flush.
		  ---------------------------------------------------------------------------*/

//		do
//			XNextEvent(display, &e);
//		while (e.type != Expose || e.xexpose.count);

		XFlush(display);
	}
	else    XMapWindow(display, window);
	/*---------------------------------------------------------------------------
	  Allocate memory for the X- and display-specific version of the image.
	  ---------------------------------------------------------------------------*/

	return 0;

} /* end function xwindow_create() */


static int ximage_create()
{
	unsigned char *xdata;

	if (depth == 24 || depth == 32) {
		xdata = (unsigned char *)malloc(4*image_width*image_height);
		pad = 32;
	} else if (depth == 16) {
		xdata = (unsigned char *)malloc(2*image_width*image_height);
		pad = 16;
	} else /* depth == 8 */ {
		xdata = (unsigned char *)malloc(image_width*image_height);
		pad = 8;
	}

	if (!xdata) {
		printf(":  unable to allocate image memory\n");
		return 4;
	}

	if (ximage) {
		if (ximage->data) {
			free(ximage->data);           /* we allocated it, so we free it */
			ximage->data = (char *)NULL;  /*  instead of XDestroyImage() */
		}
		XDestroyImage(ximage);
		ximage = NULL;
	}

	ximage = XCreateImage(display, visual, depth, ZPixmap, 0,
			(char *)xdata, image_width, image_height, pad, 0);

	if (!ximage) {
		printf(":  XCreateImage() failed\n");
		free(xdata);
		return 3;
	}

	ximage->byte_order = MSBFirst;
	return 0;
}


static int ximage_display(void)
{
	unsigned char *src;
	char *dest;
	unsigned char r, g, b, a;
	unsigned long i, row, lastrow = 0;
	unsigned long pixel;
	int ximage_rowbytes = ximage->bytes_per_line;

	if (depth == 24 || depth == 32) {
		unsigned long red, green, blue;

		for (lastrow = row = 0;  row < image_height;  ++row) {
			src = image_data + row*image_rowbytes;
			dest = ximage->data + row*ximage_rowbytes;
			if (image_channels == 3) {
				for (i = image_width;  i > 0;  --i) {
					red   = *src++;
					green = *src++;
					blue  = *src++;
#ifdef NO_24BIT_MASKS
					pixel = (red   << RShift) |
						(green << GShift) |
						(blue  << BShift);
					/* recall that we set ximage->byte_order = MSBFirst above */
					/* GRR BUG:  this assumes bpp == 32, but may be 24: */
					*dest++ = (char)((pixel >> 24) & 0xff);
					*dest++ = (char)((pixel >> 16) & 0xff);
					*dest++ = (char)((pixel >>  8) & 0xff);
					*dest++ = (char)( pixel        & 0xff);
#else
					red   = (RShift < 0)? red   << (-RShift) : red   >> RShift;
					green = (GShift < 0)? green << (-GShift) : green >> GShift;
					blue  = (BShift < 0)? blue  << (-BShift) : blue  >> BShift;
					pixel = (red & RMask) | (green & GMask) | (blue & BMask);
					/* recall that we set ximage->byte_order = MSBFirst above */
					*dest++ = (char)((pixel >> 24) & 0xff);
					*dest++ = (char)((pixel >> 16) & 0xff);
					*dest++ = (char)((pixel >>  8) & 0xff);
					*dest++ = (char)( pixel        & 0xff);
#endif
				}
			} else /* if (image_channels == 4) */ {
				for (i = image_width;  i > 0;  --i) {
					r = *src++;
					g = *src++;
					b = *src++;
					a = *src++;
					if (a == 255) {
						red   = r;
						green = g;
						blue  = b;
					} else if (a == 0) {
						red   = bg_red;
						green = bg_green;
						blue  = bg_blue;
					} else {
						/* this macro (from png.h) composites the foreground
						 * and background values and puts the result into the
						 * first argument */
						alpha_composite(red,   r, a, bg_red);
						alpha_composite(green, g, a, bg_green);
						alpha_composite(blue,  b, a, bg_blue);
					}
					pixel = (red   << RShift) |
						(green << GShift) |
						(blue  << BShift);
					/* recall that we set ximage->byte_order = MSBFirst above */
					*dest++ = (char)((pixel >> 24) & 0xff);
					*dest++ = (char)((pixel >> 16) & 0xff);
					*dest++ = (char)((pixel >>  8) & 0xff);
					*dest++ = (char)( pixel        & 0xff);
				}
			}
			/* display after every 16 lines */
			if (((row+1) & 0xf) == 0) {
				XPutImage(display, window, gc, ximage, 0, (int)lastrow, 0,
						(int)lastrow, image_width, 16);
				XFlush(display);
				lastrow = row + 1;
			}
		}

	} else if (depth == 16) {
		unsigned short red, green, blue;

		for (lastrow = row = 0;  row < image_height;  ++row) {
			src = image_data + row*image_rowbytes;
			dest = ximage->data + row*ximage_rowbytes;
			if (image_channels == 3) {
				for (i = image_width;  i > 0;  --i) {
					red   = ((unsigned short)(*src) << 8);
					++src;
					green = ((unsigned short)(*src) << 8);
					++src;
					blue  = ((unsigned short)(*src) << 8);
					++src;
					pixel = ((red   >> RShift) & RMask) |
						((green >> GShift) & GMask) |
						((blue  >> BShift) & BMask);
					/* recall that we set ximage->byte_order = MSBFirst above */
					*dest++ = (char)((pixel >>  8) & 0xff);
					*dest++ = (char)( pixel        & 0xff);
				}
			} else /* if (image_channels == 4) */ {
				for (i = image_width;  i > 0;  --i) {
					r = *src++;
					g = *src++;
					b = *src++;
					a = *src++;
					if (a == 255) {
						red   = ((unsigned short)r << 8);
						green = ((unsigned short)g << 8);
						blue  = ((unsigned short)b << 8);
					} else if (a == 0) {
						red   = ((unsigned short)bg_red   << 8);
						green = ((unsigned short)bg_green << 8);
						blue  = ((unsigned short)bg_blue  << 8);
					} else {
						/* this macro (from png.h) composites the foreground
						 * and background values and puts the result back into
						 * the first argument (== fg byte here:  safe) */
						alpha_composite(r, r, a, bg_red);
						alpha_composite(g, g, a, bg_green);
						alpha_composite(b, b, a, bg_blue);
						red   = ((unsigned short)r << 8);
						green = ((unsigned short)g << 8);
						blue  = ((unsigned short)b << 8);
					}
					pixel = ((red   >> RShift) & RMask) |
						((green >> GShift) & GMask) |
						((blue  >> BShift) & BMask);
					/* recall that we set ximage->byte_order = MSBFirst above */
					*dest++ = (char)((pixel >>  8) & 0xff);
					*dest++ = (char)( pixel        & 0xff);
				}
			}
			/* display after every 16 lines */
			if (((row+1) & 0xf) == 0) {
				XPutImage(display, window, gc, ximage, 0, (int)lastrow, 0,
						(int)lastrow, image_width, 16);
				XFlush(display);
				lastrow = row + 1;
			}
		}

	} else /* depth == 8 */ {

		/* GRR:  add 8-bit support */

	}

	Trace((stderr, "calling final XPutImage()\n"))
		if (lastrow < image_height) {
			XPutImage(display, window, gc, ximage, 0, (int)lastrow, 0,
					(int)lastrow, image_width, image_height-lastrow);
			XFlush(display);
		}

	return 0;
}



static int x_msb(unsigned long u32val)
{
	int i;

	for (i = 31;  i >= 0;  --i) {
		if (u32val & 0x80000000L)
			break;
		u32val <<= 1;
	}
	return i;
}
